﻿//---------------------------------------------------------------------
// Copyright (c) 2007 Digerati Technologies, LLC.  All Rights Reserved.
//
// Release under the Microsoft Public License (Ms-PL).
//---------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;

namespace DigeratiTech.NDiagnostics
{
    /// <summary>
    /// This class provides methods for managing diagnostic groups and tasks.
    /// </summary>
    public class TaskHandler
    {
        #region Private Attributes...

        private string _title;
        private System.Collections.Generic.List<TaskGroup> _groups = new List<TaskGroup>();

        #endregion

        #region Properties...

        public string Title
        {
            get { return _title; }
        }

        public List<TaskGroup> TaskGroups
        {
            get { return _groups; }
        }

        public int TaskCount
        {
            get
            {
                int count = 0;

                foreach (TaskGroup group in TaskGroups)
                    foreach (DiagnosticTask task in group.Tasks)
                        count++;

                return count;
            }
        }

        #endregion

        #region Methods...

        public List<TaskGroup> Load()
        {
            string configXml = DigeratiTech.NDiagnostics.Properties.Resources.NDiagnostics_xml; ;

            if (string.IsNullOrEmpty(configXml))
            {
                throw new InvalidOperationException("The diagnostics configuration resource file could not be loaded.");
            }

            XmlDocument xmlDoc = new XmlDocument();

            xmlDoc.LoadXml(configXml);

            return Load(xmlDoc);
        }

        public List<TaskGroup> Load(string filename)
        {
            if (!File.Exists(filename))
            {
                throw new FileNotFoundException("The diagnostics configuration file could not be found.", filename);
            }

            XmlDocument xmlDoc = new XmlDocument();

            xmlDoc.Load(filename);

            return Load(xmlDoc);
        }

        public List<TaskGroup> Load(XmlDocument xmlDoc)
        {
            XmlNodeList props = xmlDoc.GetElementsByTagName("NDiagnostics");
            XmlNodeList groupList = xmlDoc.GetElementsByTagName("Group");
            XmlNodeList taskList;
            TaskGroup taskGroup;
            DiagnosticTask task;

            // Load general properties
            if (props.Count > 0)
            {
                _title = GetSafeAttribute(props[0].Attributes, "title");

                if (string.IsNullOrEmpty(Title))
                {
                    _title = "NDiagnostics";
                }
            }

            // Clean up any existing groups and tasks
            foreach (TaskGroup group in _groups)
            {
                group.Tasks.Clear();
            }

            _groups.Clear();

            if (groupList == null || groupList.Count == 0)
            {
                throw new InvalidOperationException("The diagnostics configuration file must contain at least one <Group> element.");
            }

            // Iterate each <Group> element and load all nested tasks
            foreach (XmlNode groupNode in groupList)
            {
                taskGroup = new TaskGroup(groupNode.Attributes["name"].Value);

                _groups.Add(taskGroup);

                // Iterate through each nested <Task> node
                taskList = groupNode.SelectNodes("Task");

                if (taskList == null || taskList.Count == 0)
                {
                    throw new InvalidOperationException("Each <Group> element within the diagnostics configuration file must contain at least one <Task> element.");
                }

                foreach (XmlNode taskNode in taskList)
                {
                    task = GetDiagnosticTask(taskNode.Attributes["name"].Value, taskNode, taskGroup);

                    if (task != null)
                    {
                        taskGroup.Tasks.Add(task);
                    }
                }
            }

            return _groups;
        }

        /// <summary>
        /// This is a factory method that returns the correct DiagnosticTask based upon the name passed in.
        /// </summary>
        /// <param name="taskName">The name of the task to be created.</param>
        /// <returns>An instance of type <see cref="DiagnosticTask"/> representing the appropriate task class.</returns>
        /// <remarks>If the "disabled" attribute has been specified (and set to "true") for the current task, then a value of null will
        /// be returned.</remarks>
        private DiagnosticTask GetDiagnosticTask(string taskName, XmlNode taskNode, TaskGroup taskGroup)
        {
            DiagnosticTask task = null;

            // First, check to see if a "disabled" attribute has been set - if so, skip this task
            if (!GetBoolean(GetSafeAttribute(taskNode.Attributes, "disabled")))
            {
                switch (taskName.ToLower())
                {
                    case "exectask":
                    task = new ExecTask(taskGroup, taskName, taskNode.Attributes["path"].Value, GetSafeAttribute(taskNode.Attributes, "arguments"), GetSafeIntAttribute(taskNode.Attributes, "exitCode"));
                    break;

                    case "netfxtask":
                    task = new NETFXTask(taskGroup, taskName, taskNode.Attributes["version"].Value);
                    break;

                    case "filetask":
                    task = new FileTask(taskGroup, taskName, taskNode.Attributes["path"].Value, GetSafeAttribute(taskNode.Attributes, "version"), GetSafeAttribute(taskNode.Attributes, "minVersion"));
                    break;

                    case "foldertask":
                    task = new FolderTask(taskGroup, taskName, taskNode.Attributes["path"].Value);
                    break;

                    case "processrunningtask":
                    task = new ProcessRunningTask(taskGroup, taskName, taskNode.Attributes["processName"].Value, GetSafeAttribute(taskNode.Attributes, "processDescription"));
                    break;

                    case "registrytask":
                    task = new RegistryTask(taskGroup, taskName, taskNode.Attributes["baseKey"].Value, GetSafeAttribute(taskNode.Attributes, "key"), GetSafeAttribute(taskNode.Attributes, "valueName"), GetSafeAttribute(taskNode.Attributes, "value"), GetBoolean(GetSafeAttribute(taskNode.Attributes, "expandVariables")));
                    break;

                    case "roletask":
                    task = new RoleTask(taskGroup, taskName, GetSafeAttribute(taskNode.Attributes, "domain"), GetSafeAttribute(taskNode.Attributes, "userName"), taskNode.Attributes["role"].Value);
                    break;

                    case "sqlservertask":
                    task = new SqlServerTask(taskGroup, taskName, taskNode.Attributes["connectionString"].Value, GetSafeAttribute(taskNode.Attributes, "tableName"), GetSafeAttribute(taskNode.Attributes, "scalarQuery"), GetSafeAttribute(taskNode.Attributes, "scalarValue"));
                    break;

                    case "urltask":
                    task = new UrlTask(taskGroup, taskName, taskNode.Attributes["url"].Value);
                    break;

                    case "windowsservicetask":
                    task = new WindowsServiceTask(taskGroup, taskName, taskNode.Attributes["serviceName"].Value, GetSafeAttribute(taskNode.Attributes, "serviceStatus"), GetSafeAttribute(taskNode.Attributes, "startupType"), GetSafeAttribute(taskNode.Attributes, "startupAccount"));
                    break;

                    default:
                    throw new InvalidOperationException("An invalid diagnostics <Task> element was found in the configuration file: '" + taskName + "'.");
                }

                if (task != null)
                {
                    task.Description = GetSafeAttribute(taskNode.Attributes, "description");

                    // Allow any task to treat a failure as a warning - if flagged appropriately in the configuration file
                    task.FailAsWarning = GetBoolean(GetSafeAttribute(taskNode.Attributes, "failAsWarning"));
                }
            }

            return task;
        }

        /// <summary>
        /// Gets the specified attribute value or null if the attribute isn't found.
        /// </summary>
        /// <param name="attributes">A collection of <see cref="XmlAttribute"/> to search.</param>
        /// <param name="name">The attribute name to search for.</param>
        /// <returns>The specified attribute value if found; otherwise, null.</returns>
        private string GetSafeAttribute(XmlAttributeCollection attributes, string name)
        {
            string value = null;

            foreach (XmlAttribute attribute in attributes)
            {
                if (attribute.Name.Equals(name))
                {
                    value = (attribute.Value.Length == 0) ? null : attribute.Value;
                    break;
                }
            }

            return value;
        }

        /// <summary>
        /// Gets the specified attribute value as an <see cref="int"/> type or zero (0) if the attribute isn't found.
        /// </summary>
        /// <param name="attributes">A collection of <see cref="XmlAttribute"/> to search.</param>
        /// <param name="name">The attribute name to search for.</param>
        /// <returns>The specified attribute value if found; otherwise, null.</returns>
        private int GetSafeIntAttribute(XmlAttributeCollection attributes, string name)
        {
            int result = 0;
            string value = GetSafeAttribute(attributes, name);

            int.TryParse(value, out result);

            return result;
        }

        /// <summary>
        /// Returns the total task count matching the <paramref name="status"/> passed in.
        /// </summary>
        /// <param name="status">The status value to match.</param>
        /// <returns>The total task count matching the <paramref name="status"/> passed in.</returns>
        public int GetStatusCount(TaskStatus status)
        {
            int count = 0;

            foreach (TaskGroup group in TaskGroups)
                foreach (DiagnosticTask task in group.Tasks)
                    if (task.Status == status)
                        count++;

            return count;
        }

        /// <summary>
        /// Converts a string value into a boolean representation.
        /// </summary>
        /// <param name="value">The value to convert.</param>
        /// <returns>A boolean value representing the string value passed in.</returns>
        /// <remarks>The following will be treated as <c>true</c>: "Yes", "True", "1".</remarks>
        private bool GetBoolean(string value)
        {
            bool flag = false;

            if (!string.IsNullOrEmpty(value))
            {
                value = value.Trim().ToLower();

                flag = (value[0] == 'y' || value[0] == 't' || value[0] == '1');
            }

            return flag;
        }
        #endregion
    }
}
